﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace MadLibrary.Net.PeerToPeer
{
    /// <summary>
    /// Serves as communicator between two peerers.
    /// </summary>
    public class Peerer
    {
        /// <summary>
        /// Gets or sets the peerer's informations.
        /// </summary>
        /// <value>
        /// The peerer's informations.
        /// </value>
        public PeererInfo Informations { get; set; }

        /// <summary>
        /// Gets or sets the peerer's socket.
        /// </summary>
        /// <value>
        /// The peerer's socket.
        /// </value>
        protected Socket Socket { get; set; }

        /// <summary>
        /// Gets or sets the peerer's listener.
        /// </summary>
        /// <value>
        /// The peerer's listener.
        /// </value>
        protected Socket Listener { get; set; }

        /// <summary>
        /// Gets or sets the peerer's listening thread.
        /// </summary>
        /// <value>
        /// The peerer's listening thread.
        /// </value>
        protected Thread ListeningThread { get; set; }

        /// <summary>
        /// Initializes a new instance of the <see cref="Peerer"/> class.
        /// </summary>
        public Peerer()
        {
            this.Informations = new PeererInfo();
            this.Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            this.Listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Peerer"/> class with specified PeererInfo.
        /// </summary>
        /// <param name="Info">The peerer's info.</param>
        public Peerer(PeererInfo Info)
        {
            this.Informations = Info;
            this.Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            this.Listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Peerer"/> class with specified PeererInfo and specified peerer's socket.
        /// </summary>
        /// <param name="Info">The peerer's info.</param>
        /// <param name="Socket">The peerer's socket.</param>
        public Peerer(PeererInfo Info, Socket Socket)
        {
            this.Informations = Info;
            this.Socket = Socket;
            this.Listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        }

        /// <summary>
        /// Connects this instance.
        /// </summary>
        public void Connect()
        {
            this.ListeningThread = new Thread(new ThreadStart(() =>
            {
                this.Listener.Bind(this.Informations.Target);
                this.Listener.Listen(1);
                Socket client = this.Listener.Accept();
                this.Listener = client;
            }));
            this.ListeningThread.Start();
            this.Socket.Connect(this.Informations.Target);
            this.Informations.StartCommunication();
        }

        /// <summary>
        /// Sends the specified message.
        /// </summary>
        /// <param name="Message">Specifies message which has to be sent.</param>
        public void Send(string Message)
        {
            this.Socket.Send(this.Informations.Encoding.GetBytes(Message));
        }

        /// <summary>
        /// Sends the specified bytes.
        /// </summary>
        /// <param name="bytes">Specifies bytes which has to be sent.</param>
        public void Send(byte[] bytes)
        {
            this.Socket.Send(bytes);
        }

        /// <summary>
        /// Receives the specified bytes.
        /// </summary>
        /// <param name="Bytes">Specifies bytes which has to be received.</param>
        /// <returns>The received string.</returns>
        public string Receive(ref byte[] Bytes)
        {
            this.Socket.Receive(Bytes);
            return this.Informations.Encoding.GetString(Bytes);
        }

        /// <summary>
        /// Receives the specified byte count.
        /// </summary>
        /// <param name="ByteCount">The byte count.</param>
        /// <returns>The received string.</returns>
        public string Receive(int ByteCount)
        {
            byte[] b = new byte[ByteCount];
            this.Listener.Receive(b);
            return this.Informations.Encoding.GetString(b);
        }

        /// <summary>Receives the specified byte count.</summary>
        /// <param name="ByteCount">The byte count.</param>
        /// <param name="Bytes">[out] The bytes.</param>
        /// <returns>Received bytes.</returns>
        public string Receive(int ByteCount, out byte[] Bytes)
        {
            byte[] b = new byte[ByteCount];
            this.Socket.Receive(b);
            Bytes = b;
            return this.Informations.Encoding.GetString(b);
        }

        /// <summary>
        /// Disconnects this instance.
        /// </summary>
        public void Disconnect()
        {
            this.ListeningThread.Abort();
            this.Socket.Disconnect(true);
            this.Informations.EndCommunication();
        }

        /// <summary>
        /// Disconnects this instance and gets out PeererInfo.
        /// </summary>
        /// <param name="Info">The peerer's info.</param>
        public void Disconnect(out PeererInfo Info)
        {
            this.Disconnect();
            Info = this.Informations;
        }

        /// <summary>
        /// Releases unmanaged resources and performs other cleanup operations before the
        /// <see cref="Peerer"/> is reclaimed by garbage collection.
        /// </summary>
        ~Peerer()
        {
            this.Disconnect();
        }
    }
}